package jam4j.util;

import java.util.*;

public abstract class Strings {
    private Strings() { }
    
    public static String toString(Object object) {
        if (object == null)
            return "null";
        if (!object.getClass().isArray())
            return object.toString();
        if (object instanceof int[])
            return Arrays.toString((int[]) object);
        if (object instanceof char[])
            return Arrays.toString((char[]) object);
        if (object instanceof byte[])
            return Arrays.toString((byte[]) object);
        if (object instanceof double[])
            return Arrays.toString((double[]) object);
        if (object instanceof boolean[])
            return Arrays.toString((boolean[]) object);
        if (object instanceof long[])
            return Arrays.toString((long[]) object);
        if (object instanceof short[])
            return Arrays.toString((short[]) object);
        if (object instanceof float[])
            return Arrays.toString((float[]) object);
        
        return Arrays.deepToString((Object[]) object);
    }
    
    public static <T> StringBuilder join(
            StringBuilder builder, String delim, T ... objects) {
        if (objects == null || objects.length == 0)
            return builder;
        
        final int count = objects.length;
        for (int ix = 0;;) {
            builder.append(toString(objects[ix]));
            
            if (++ix == count)
                return builder;
            
            builder.append(delim);
        }
    }
    
    public static <T> StringBuilder join(
            StringBuilder builder, String delim, Iterable<T> objects) {
        if (objects == null)
            return builder;
        
        final Iterator<T> iter = objects.iterator();
        if (!iter.hasNext())
            return builder;
        
        while (true) {
            builder.append(toString(iter.next()));
            
            if (!iter.hasNext())
                return builder;
            
            builder.append(delim);
        }
    }
    
    public static <T> String join(String delim, T ... objects) {
        return
            objects == null || objects.length == 0 ? "" :
            objects.length == 1 ? String.valueOf(objects[0]) :
                join(new StringBuilder(), delim, objects).toString();
    }
    
    public static <T> StringBuilder rightJoin(
            StringBuilder builder, String delim, T ... objects) {
        if (objects != null)
            for (T object : objects)
                builder.append(toString(object)).append(delim);
        return builder;
    }
    
    public static <T> String rightJoin(String delim, T ... objects) {
        return rightJoin(new StringBuilder(), delim, objects).toString();
    }
    
    public static Object rightJoiner(
            final String delim, final Object ... objects) {
        return new Object() {
            public String toString() {
                return rightJoin(delim, objects);
            }
        };
    }
    
    private static final String[] EMPTY_ARRAY = new String[0];
    
    public static String[] outerProduct(String[] ... arrays) {
        if (arrays == null)
            return EMPTY_ARRAY;
        
        final int count = arrays.length;
        
        final int[] lengths = new int[count];
        final int ansCount;
        {
            int prod = 1;
            for (int ix = 0; ix < count; ix++)
                prod *= (lengths[ix] = arrays[ix].length);
            ansCount = prod;
        }
        
        // Trivial cases
        switch (ansCount) {
            case 0:
                return EMPTY_ARRAY;
            case 1:
                // This also covers, in a roundabout way, the case of
                // arrays.length == 0
                final StringBuilder builder = new StringBuilder();
                for (String[] array : arrays)
                    builder.append(array[0]);
                return new String[] { builder.toString() };
        }
        
        final String[] ans = new String[ansCount];
        final StringBuilder builder = new StringBuilder();
        final int[] ixs = new int[count], pos = new int[count];
        
        int ansIx = 0, arrayIx = 0;
        
        while (true) {
            if (ixs[arrayIx] == lengths[arrayIx]) {
                // Backtrack
                ixs[--arrayIx]++;
                builder.setLength(pos[arrayIx]);
            } else if (arrayIx + 1 == count) {
                ans[ansIx++] = 
                    builder.append(arrays[arrayIx][ixs[arrayIx]]).toString();
                
                if (ansIx == ansCount)
                    return ans;
                
                builder.setLength(pos[arrayIx]);
                ixs[arrayIx]++;
            } else {
                builder.append(arrays[arrayIx][ixs[arrayIx]]);
                ixs[++arrayIx] = 0;
                pos[arrayIx] = builder.length();
            }
        }
    }
    
    public static StringBuilder prettyQuote(
            Object object, StringBuilder builder) {
        return builder.append('“').append(object).append('”');
    }
    
    public static String prettyQuote(Object object) {
        final String string = String.valueOf(object);
        final int len = string.length();
        final char[] chars = new char[len + 2];
        chars[0] = '“';
        chars[len + 1] = '”';
        string.getChars(0, len, chars, 1);
        return string;
    }
    
    public static int indexOfOutside(
            String substr, String str, int ix,
            String leftDelim, String rightDelim) {
        final int len = str.length();
        int depth = 0;
        while (ix < len)
            if (depth == 0 && str.startsWith(substr, ix))
                return ix;
            else if (str.startsWith(leftDelim, ix)) {
                depth++;
                ix += leftDelim.length();
            } else if (depth > 0 && str.startsWith(rightDelim, ix)) {
                depth--;
                ix += rightDelim.length();
            } else
                ix++;
        return -1;
    }
}